﻿
namespace Orchard.OData.Services {
    using Microsoft.OData.Core;
    using Microsoft.OData.Core.UriParser;
    using Microsoft.OData.Edm;
    using NHibernate.Criterion;
    using Orchard.ContentManagement;
    using Orchard.ContentManagement.Records;
    using Orchard.Data;
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Linq;
    using System.Net;
    using System.Web;

    public interface IDataRequestBroker : IDependency {
        IEnumerable<ContentItem> ExecuteQuery(HttpRequest httpRequest, IEdmModel edmModel);
        ODataEntry BuildEntry(ContentItem contentItem, IEdmModel edmModel);
    }

    public class DataRequestBroker : IDataRequestBroker {
        private readonly ISessionLocator _sessionLocator;
        private readonly IContentManager _contentManager;
        public DataRequestBroker(
            ISessionLocator sessionLocator,
            IContentManager contentManager) {
            this._sessionLocator = sessionLocator;
            this._contentManager = contentManager;
        }

        IEnumerable<ContentItem> IDataRequestBroker.ExecuteQuery(HttpRequest httpRequest, IEdmModel edmModel) {
            var intParsedValue = int.MinValue;

            var requestUri = httpRequest.Url;
            var requestQueryArgs = HttpUtility.ParseQueryString(requestUri.Query);
            
            var rootODataUri = httpRequest.ODataRootService();
            var lastSegment = requestUri.Segments.Last();
            
            var contentIdProvided = lastSegment.SkipWhile(c => c != '(').TakeWhile(c => c != ')').ToArray();
            if (contentIdProvided.Any()) {
                contentIdProvided = contentIdProvided.Skip(1).ToArray(); // == remove '('
            }

            var entitySetName = lastSegment.Split("(".ToArray()).First();
            var edmEntitySet = edmModel.FindEntityContainer(MetadataBuilder._qualifiedContainer)
                .EntitySets()
                .FirstOrDefault(es => es.Name.ToLower() == entitySetName.ToLower());

            if (null == edmEntitySet) {
                throw new ODataErrorException(new ODataError() { 
                    ErrorCode = HttpStatusCode.NotFound.ToString() });
            }

            var edmEntityType = edmEntitySet.EntityType();

            var session = this._sessionLocator.For(typeof(ContentItem));
            var contentItemVersionRecordCriteria = session.CreateCriteria<ContentItemVersionRecord>();
            contentItemVersionRecordCriteria.Add(Restrictions.Eq("Published", true));

            var contentItemRecordCriteria = contentItemVersionRecordCriteria.BindCriteriaByPath("ContentItemRecord");
            var contentTypeCriteria = contentItemRecordCriteria.BindCriteriaByPath("ContentType");
            contentTypeCriteria.Add(Restrictions.Eq("Name", edmEntityType.Name));

            if (contentIdProvided.Any() && int.TryParse(new string(contentIdProvided), out intParsedValue)) {
                return new List<ContentItem> {
                    this._contentManager.Get<ContentItem>(intParsedValue, VersionOptions.Published, QueryHints.Empty)
                };
            }

            var odataParser = new ODataUriParser(edmModel, rootODataUri, requestUri);

            var filter = requestQueryArgs["$filter"];
            if (!string.IsNullOrEmpty(filter)) {
                var filterClause = odataParser.ParseFilter();
                contentItemVersionRecordCriteria = contentItemVersionRecordCriteria.FilterContentQuery(filterClause, edmModel);
            }

            var orderBy = requestQueryArgs["$orderby"];
            if (!string.IsNullOrEmpty(orderBy)) {
                var orderByClause = odataParser.ParseOrderBy();
                contentItemVersionRecordCriteria = contentItemVersionRecordCriteria.OrderContentQuery(orderByClause, edmModel);
            }

            var skip = requestQueryArgs["$skip"];
            if (!string.IsNullOrEmpty(skip) && int.TryParse(skip, out intParsedValue)) {
                contentItemVersionRecordCriteria.SetFirstResult(intParsedValue);
            }

            var top = requestQueryArgs["$top"];
            if (!string.IsNullOrEmpty(top) && int.TryParse(top, out intParsedValue)) {
                contentItemVersionRecordCriteria.SetMaxResults(intParsedValue);
            }

            var hQueryResults = contentItemVersionRecordCriteria.List();
            var contentItemRecordIds = hQueryResults
                .Cast<ContentItemVersionRecord>()
                .Select(civr => civr.ContentItemRecord.Id)
                .Distinct()
                .ToList();

            return this._contentManager.GetMany<ContentItem>(contentItemRecordIds, VersionOptions.Published, QueryHints.Empty);
        }


        ODataEntry IDataRequestBroker.BuildEntry(ContentItem contentItem, IEdmModel edmModel) {
            var structuredType = edmModel.FindType(MetadataBuilder._namespace + "." + contentItem.TypeDefinition.Name) as IEdmEntityType;
            var structuralProperties = structuredType.StructuralProperties();
            return new ODataEntry() {
                Properties = structuralProperties.Select(sp => this.BuildODataProperty(contentItem, sp.Name, edmModel)).Where(p => null != p.Value).ToList(),
            };
        }

        private ODataProperty BuildODataProperty(object instance, string propertyName, IEdmModel edmModel) {          
            object propertyValue = null;
            var instanceProperties = instance.GetType().GetProperties();

            if (instanceProperties.Any(prop => prop.Name == propertyName)) {
                propertyValue = instanceProperties.FirstOrDefault(p => p.Name == propertyName).GetValue(instance, null);
            }

            if (null == propertyValue && instance is ContentItem) {
                var contentItem = instance as ContentItem;
                var contentPart = (instance as ContentItem).Parts.FirstOrDefault(p => p.PartDefinition.Name == propertyName);
                propertyValue = contentPart;
            }

            if (null == propertyValue && instance is ContentItem) {
                var fieldSpecificPart = (instance as ContentItem).Parts.FirstOrDefault(p => p.PartDefinition.Name == (instance as ContentItem).TypeDefinition.Name);
                propertyValue = fieldSpecificPart.Fields.FirstOrDefault(f => f.Name == propertyName);
            }

            return new ODataProperty() {
                Name = propertyName,
                Value = this.BuildODataValue(propertyValue, edmModel),
            };
        }

        private object BuildODataValue(object value, IEdmModel edmModel) {
            if (value == null) {
                return null;
            }

            IEdmType structuredType = null;
            if (value is ContentPart) {
                structuredType = edmModel.FindType(MetadataBuilder._namespace + "." + (value as ContentPart).PartDefinition.Name) as IEdmType;
            }
            if (value is ContentField) {
                structuredType = edmModel.FindType(MetadataBuilder._namespace + "." + (value as ContentField).Name) as IEdmType;
            }

            if (null == structuredType) {
                var structuredTypeReference = edmModel.GetEdmTypeReference(value.GetType());
                structuredType = structuredTypeReference.Definition;
            }            
            if (structuredType.TypeKind == EdmTypeKind.Primitive) {
                return new ODataPrimitiveValue(value.ConvertUnsupportedPrimitives());
            }
            if (structuredType.TypeKind == EdmTypeKind.Enum) {
                return new ODataEnumValue(value.ToString(), value.GetType().FullName);
            }
            if (structuredType.TypeKind == EdmTypeKind.Collection) {
                structuredType = (structuredType as IEdmCollectionType).ElementType.Definition;
                var genericTypeName = "Collection(" + structuredType.FullTypeName() + ")";
                ICollection<object> collectionItems = new Collection<object>();
                foreach (var o in (value as IEnumerable)) {
                    collectionItems.Add(this.BuildODataValue(o, edmModel));
                }
                return new ODataCollectionValue() { TypeName = genericTypeName, Items = collectionItems };
            }
            if (structuredType.TypeKind == EdmTypeKind.Complex) {
                return new ODataComplexValue() {
                    TypeName = structuredType.FullTypeName(),
                    Properties = (structuredType as IEdmComplexType).StructuralProperties().Select(p => this.BuildODataProperty(value, p.Name, edmModel)), 
                };
            }

            throw new ODataErrorException(new ODataError() { 
                ErrorCode = HttpStatusCode.NotImplemented.ToString(), 
                InnerError = new ODataInnerError(new NotImplementedException()) });
        }
    }
}